Introducción

Analizar texto se ha convertido en una destreza que permite descubrir características y a la vez entender a los creadores del texto. Por ejemplo, podemos analizar el texto determinando las palabras más frecuentes, realizar un análisis de sentimientos y clasificar el contenido por clases, determinar la relación entre usuarios de redes sociales, entre otros.

Los datos

Utilizando Developer Platform de Twitter se procedió a crear la conexión para realizar búsquedas de información relacionada con líderes socialistas latinoamericanos que han gobernado o han participado en elecciones durante los últimos 20 años. El paquete utilizado para este fin es rtweet. Salvo una excepción, se realizó una doble búsqueda por cada líder, para esto se utilizó a get_timeline del paquete rtweet. Obtenidos los datos se realizó una selección de campos con el fin de realizar un análisis de tweets propios y retweets.

Los líderes seleccionados son:

No. Nombres Usuario País
1 Andrés Arauz ecuarauz Ecuador
2 Alberto Fernández alferdez Argentina
3 Andrés Manuel lopezobrador_ México
4 Cristina Kirchner CFKArgentina Argentina
5 Miguel Díaz-Canel Bermúdez DiazCanelB Cuba
6 Dilma Rousseff dilmabr Brasil
7 Ernesto Samper ernestosamperp Colombia
8 Evo Morales evoespueblo Bolivia
9 Fernando Lugo lugo_py Paraguay
10 Gabriel Boric gabrielboric Chile
11 Gustavo Petro petrogustavo Colombia
12 Hugo Chávez chavezcandanga Venezuela
13 Luis Arce LuchoXBolivia Bolivia
14 Lula LulaOficial Brasil
15 Manuel Zelaya manuelzr Honduras
16 Nicolás Maduro NicolasMaduro Venezuela
17 Ollanta Humala Ollanta_HumalaT Perú
18 Pedro Castillo PedroCastilloTe Perú
19 Rafael Correa MashiRafael Ecuador
20 Xiomara Castro de Zelaya XiomaraCastroZ Honduras

Dependiendo del análisis se consideró a todos los usuarios como uno solo, sin embargo, lo ideal es trabajar por separado para cada usuario para determinar las diferencias y similitudes que puedan existir. Los datos recopilados cuentan con 58512 registros que entre sus principales características permiten determinar si un tweet es propio o retweet.

A continuación, la carga de los datos y la visualización de los primeros registros:

Descripción del tipo de tweet.

El campo texto almacena a cada tweet recopilado, es importante notar que un tweet puede ser de autoría propia o no, en este caso hablamos de un retweet. Para esto se utilizó al paquete tidyverse que debes aprender si te interesa manipular datos con R. A continuación, la cantidad de tweets por tipo y por usuario:

library(tidyverse)
Registered S3 methods overwritten by 'dbplyr':
  method         from
  print.tbl_lazy     
  print.tbl_sql      
-- Attaching packages -------------------------------------------------------------------- tidyverse 1.3.1 --
v ggplot2 3.3.5     v purrr   0.3.4
v tibble  3.1.3     v dplyr   1.0.7
v tidyr   1.1.3     v stringr 1.4.0
v readr   2.0.0     v forcats 0.5.1
-- Conflicts ----------------------------------------------------------------------- tidyverse_conflicts() --
x dplyr::filter() masks stats::filter()
x dplyr::lag()    masks stats::lag()
merge(x = tweets %>% #tweets propios
        select(screen_name, is_retweet) %>% #selección de campos
        filter(is_retweet == FALSE) %>% #filtro para tweet propio
        count(screen_name, name = "n_propios"), #cantidad de tweets propios
      y = tweets %>%  #retweets
        select(screen_name, is_retweet) %>% #selección de campos
        filter(is_retweet == TRUE) %>% #filtro para retweet
        count(screen_name, name = "n_retweets"), #cantidad de retweets
      all = TRUE) %>% #unión de dataframes
  arrange(screen_name) #orden por screen_name

Basado en el resultado anterior, existen usuarios que no realizan retweets, entonces se analiza nuevamente a todos los usuarios como uno solo. A continuación, la cantidad de tweets propios y retweets:

# Propios
tweets_propio = tweets %>% filter(is_retweet==FALSE)
# Retweets
tweets_retweets = tweets %>% filter(is_retweet==TRUE)
# Cantidad de tweets propios y de retweets
CantidadTipo = data.frame(tipo=c("Propio","Retweets"), #campo tipo de tweet
                         cantidad=c(nrow(tweets_propio), nrow(tweets_retweets)) #campo cantidad de cada tipo
                         )
CantidadTipo

Para visualizar está relación introducimos un peso a cada tipo, en este caso su porcentaje, y creamos un gráfico por bloques. Para esto se utiliza al paquete waffle que entre sus funciones permite realizar un gráfico por bloques.

# Aumentando peso a cada tipo
CantidadTipo = CantidadTipo %>% 
  mutate(peso=round(cantidad/sum(cantidad)*100,00)) %>% #Creacion de campo peso
  arrange(desc(cantidad)) #ordenando por cantidad

# Extraccion del peso
parte = CantidadTipo$peso
# Etiquetando por clase
names(parte) = CantidadTipo$tipo
# Grafica de tipo de tweet
library(waffle)
waffle(parte, 
       rows = 5, #número de filas 
       title = 'Tipo de tweets', 
       colors = colorRampPalette(c('blue', 'red'))(2), #paleta de colores
       # (2) indica el número de clases que va a graficar
       pad = 1, #densidad
       size = 0.5, #separacion entre bloques  
       flip = FALSE,#orientacion vertical con TRUE
       xlab =  "Fuente: Twitter"
       )

Observamos que es su mayoría se crean tweets propios.

Tweets populares

Se determina el tweet propio más popular por usuario, identificando a la cantidad de me gusta recibidos.

tweets %>% 
  filter(is_retweet == FALSE) %>% #filtro para tweet propio
  group_by(screen_name) %>% #agrupacion por screen_name
  mutate(popular = max(favorite_count)) %>% #creación de campo
  filter(favorite_count == popular) %>% #filtro para seleccionar al tweet más popular
  select(screen_name, popular, text) %>% # selección de campos
  arrange(desc(popular)) #orden por popular

El retweet más popular por usuario también es identificado.

tweets %>% 
  filter(is_retweet == TRUE) %>% #filtro para retweet
  group_by(screen_name) %>% #agrupacion por screen_name
  mutate(popular = max(retweet_favorite_count)) %>% #creación de campo
  filter(retweet_favorite_count == popular) %>% #filtro para seleccionar al tweet más popular
  select(screen_name, popular, text) %>% # selección de campos
  arrange(desc(popular)) #orden por popular

Análisis de hashtags

Dentro de cada publicación es común añadir algún hashtag. Se determinó los 20 hashtags más populares por tipo de tweet considerando a todos como uno. Primero los hashtags relacionados con tweets propios.

tweets_hashtag <- tweets_propio %>% 
  filter(!is.na(hashtags)) %>% 
  select(hashtags)

tibble(word = unlist(tweets_hashtag$hashtags)) %>% #extraer hashtag
  count(word, name = "cantidad", sort = TRUE) %>% #contar hashtags
  head(20) %>% #seleccionar los 20 primeros
  mutate(word1 = reorder(word, cantidad)) %>% #ordenar
  ggplot(aes(x = word1, y = cantidad)) + #crear la grafica
  geom_col(colour = "white", fill = "blue") + #anade la gráfica de cantidad
  xlab(NULL) + #quita el nombre del eje horizontal
  coord_flip() + #grafica en vertical
  theme_light()  + #tema seleccionado
  labs(y = "Frecuencia",
       x = "Hashtags",
       title = paste("Hashtags más frecuentes en tweets propios"),
       subtitle = "Cantidad de hashtags", 
       caption = "Fuente: Twitter")

A continuación, los hashtags relacionados con los retweets.

tweets_hashtag_r <- tweets_retweets %>% 
  filter(!is.na(hashtags)) %>% 
  select(hashtags)

tibble(word = unlist(tweets_hashtag_r$hashtags)) %>% #extraer hashtag
  count(word, name = "cantidad", sort = TRUE) %>% #contar hashtags
  head(20) %>% #seleccionar los 20 primeros
  mutate(word1 = reorder(word, cantidad)) %>% #ordenar
  ggplot(aes(x = word1, y = cantidad)) + #crear la grafica
  geom_col(colour = "white", fill = "blue") + #anade la gráfica de cantidad
  xlab(NULL) + #quita el nombre del eje horizontal
  coord_flip() + #grafica en vertical
  theme_light()  + #tema seleccionado
  labs(y = "Frecuencia",
       x = "Hashtags",
       title = paste("Hashtags más frecuentes en retweets"),
       subtitle = "Cantidad de hashtags", 
       caption = "Fuente: Twitter")

Como se observa, los hashtags en su mayoría se diferencian de tweets propios con retweets.

Frecuencia de los tweets

Otra descripción interesante es determinar la frecuencia en que se publica. A continuación, la representación gráfica de la frecuencia de tweets propios por día, para esto se utilizó a ts_plot de rtweet, es importante notar que también se puede realizar este análisis por semana, mes, año simplemente cambiando la selección en el argumento by de ts_plot.

library(rtweet)
Warning: package ‘rtweet’ was built under R version 4.1.1

Attaching package: ‘rtweet’

The following object is masked from ‘package:purrr’:

    flatten
tweets_propio %>% 
  ts_plot(by = "days", color = "blue") +
  theme_light()+
  labs(title = "Frecuencia de tweets propios",
       subtitle = "Cantidad de tweets por día",
       x = "Fecha",
       y = "Cantidad",
       caption = "Fuente: Twitter")

De forma similar, la representación gráfica de la frecuencia de retweets.

tweets_retweets %>% 
  ts_plot(by = "days", color = "blue") +
  theme_light()+
  labs(title = "Frecuencia de retweets",
       subtitle = "Cantidad de retweets por día",
       x = "Fecha",
       y = "Cantidad",
       caption = "Fuente: Twitter")

Palabras más usadas

Otras descripciones usuales son determinar la cantidad de palabras utilizadas, las palabras más populares y su representación. Para realizarlas, es importante limpiar a los datos, para esto se seleccionó a los paquetes tidytext y tm. El procedimiento es iniciar removiendo caracteres que no tengan un significado concreto. Se ha seguido el siguiente orden: remover hipervínculos, remover menciones, remover separadores, remover números y remover espacios en blanco. Luego, es importante remover acentos y cambiar todas las letras a minúsculas o mayúsculas.

library(tidytext)
library(tm)
Loading required package: NLP

Attaching package: ‘NLP’

The following object is masked from ‘package:ggplot2’:

    annotate
palabras <- tweets_propio %>%
  filter(is_retweet == FALSE) %>%
  select(text) %>%
  mutate(text=str_replace_all(text, "https\\S*", "")) %>% #remover hipervínculos
  mutate(text=str_replace_all(text, "@\\S*", "")) %>% #remover menciones
  mutate(text=str_replace_all(text, "[\r\n\t]", "")) %>% #remover separadores
  mutate(text=removeNumbers(text)) %>% #remover números
  mutate(text=removePunctuation(text)) %>% #remover puntuacion
  mutate(text=str_squish(text)) %>% #remover espacios en blanco
  mutate(text=tolower(text)) #a minúsculas

Con el texto limpio, se procede a realizar la tokenización. Esto significa que vamos a dividir a cada tweet en unidades de texto, en este caso en palabras. Existe una interesante teoría al respecto, ya que si se desea las unidades pueden ser dos palabras seguidas u oraciones, por ejemplo, si alguien se interesa puede leer al respecto en el capítulo 1 de Text Mining with R: A Tidy Approach.

palabras <- palabras %>% 
  unnest_tokens(word, text, to_lower = F) #tokenizacion

palabras

Al visualizar las primeras palabras obtenidas en el dataframe palabras se observan palabras como “la”, “de”, “es”, entre otras, que llaman la atención ya que uno esperaría que aparezcan muchas veces en un determinado texto. Una práctica común es remover a este tipo de palabras, denominadas stopwords. En la documentación de los paquetes utilizados se puede aprender un poco más al respecto. En este caso, al contar con usuarios que hablan español, inglés y portugués se removieron stopwords para estos tres idiomas. Además, se contabilizó a cada palabra y ordenó en forma descendente considerando el número de apariciones.

palabras <- palabras %>% 
  filter(!word %in% stopwords("spanish")) %>%
  filter(!word %in% stopwords("portuguese")) %>%
  filter(!word %in% stopwords("english")) %>%
  count(word, sort = TRUE) %>%
  mutate(word = reorder(word, n)) %>%
  arrange(desc(n))

palabras

Observemos que las palabras “é” y “ser” aparecen, esto es algo que en principio no esperamos suceda, sin embargo, aprovechamos su aparición para indicar como remover palabras especiales. Simplemente se determina un nuevo conjunto de palabras a ser removidas, en este caso solamente se consideró a las palabras mencionadas antes, pero si se trabaja con algún otro texto que incluya muchos símbolos o palabras especiales como variables con subíndices, el considerar está opción puede ser de gran utilidad.

my_stopword <- tibble(word = c("é", "ser"))

palabras <- palabras %>% 
  anti_join(my_stopword)
Joining, by = "word"
palabras

Para finalizar, se creó una nube de palabras que es una herramienta visual muy amigable que permite determinar por el tamaño de la palabra su importancia ya que representa así su frecuencia de aparición. Para esto se utilizó al paquete wordcloud2 que cuenta con características que permiten realizar nubes muy amigables. Por ejemplo, usar un fondo gris, colores pastel para las palabras y darle una forma de corazón.

library(wordcloud2)
Registered S3 methods overwritten by 'htmltools':
  method               from         
  print.html           tools:rstudio
  print.shiny.tag      tools:rstudio
  print.shiny.tag.list tools:rstudio
Registered S3 method overwritten by 'htmlwidgets':
  method           from         
  print.htmlwidget tools:rstudio
palabras %>% 
  select(word, freq=n) %>% 
  wordcloud2(shape = 'cardioid', size = 0.25, color = "random-light", backgroundColor = "grey")

El fin de este archivo y los que vendrán es compartir lo que he aprendido. Es posible hacer análisis más profundos y detallados que espero seguir compartiendo, por ejemplo, el realizar un análisis de sentimientos y un clasificador basado en un análisis de sentimientos.

Elaborado por Jairo Rojas.

LS0tDQp0aXRsZTogIkFuw6FsaXNpcyBkZSB0ZXh0byBjb24gUiINCm91dHB1dDogaHRtbF9ub3RlYm9vaw0KLS0tDQoNCiMgSW50cm9kdWNjacOzbg0KDQpBbmFsaXphciB0ZXh0byBzZSBoYSBjb252ZXJ0aWRvIGVuIHVuYSBkZXN0cmV6YSBxdWUgcGVybWl0ZSBkZXNjdWJyaXIgY2FyYWN0ZXLDrXN0aWNhcyB5IGEgbGEgdmV6IGVudGVuZGVyIGEgbG9zIGNyZWFkb3JlcyBkZWwgdGV4dG8uIFBvciBlamVtcGxvLCBwb2RlbW9zIGFuYWxpemFyIGVsIHRleHRvIGRldGVybWluYW5kbyBsYXMgcGFsYWJyYXMgbcOhcyBmcmVjdWVudGVzLCByZWFsaXphciB1biBhbsOhbGlzaXMgZGUgc2VudGltaWVudG9zIHkgY2xhc2lmaWNhciBlbCBjb250ZW5pZG8gcG9yIGNsYXNlcywgZGV0ZXJtaW5hciBsYSByZWxhY2nDs24gZW50cmUgdXN1YXJpb3MgZGUgcmVkZXMgc29jaWFsZXMsIGVudHJlIG90cm9zLg0KDQojIExvcyBkYXRvcw0KDQpVdGlsaXphbmRvIFtEZXZlbG9wZXIgUGxhdGZvcm1dKGh0dHBzOi8vZGV2ZWxvcGVyLnR3aXR0ZXIuY29tKSBkZSBbVHdpdHRlcl0oaHR0cHM6Ly90d2l0dGVyLmNvbSkgc2UgcHJvY2VkacOzIGEgY3JlYXIgbGEgY29uZXhpw7NuIHBhcmEgcmVhbGl6YXIgYsO6c3F1ZWRhcyBkZSBpbmZvcm1hY2nDs24gcmVsYWNpb25hZGEgY29uIGzDrWRlcmVzIHNvY2lhbGlzdGFzIGxhdGlub2FtZXJpY2Fub3MgcXVlIGhhbiBnb2Jlcm5hZG8gbyBoYW4gcGFydGljaXBhZG8gZW4gZWxlY2Npb25lcyBkdXJhbnRlIGxvcyDDumx0aW1vcyAyMCBhw7Fvcy4gRWwgcGFxdWV0ZSB1dGlsaXphZG8gcGFyYSBlc3RlIGZpbiBlcyBbcnR3ZWV0XShodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZy93ZWIvcGFja2FnZXMvcnR3ZWV0KS4gU2Fsdm8gdW5hIGV4Y2VwY2nDs24sIHNlIHJlYWxpesOzIHVuYSBkb2JsZSBiw7pzcXVlZGEgcG9yIGNhZGEgbMOtZGVyLCBwYXJhIGVzdG8gc2UgdXRpbGl6w7MgYSAqZ2V0X3RpbWVsaW5lKiBkZWwgcGFxdWV0ZSBbcnR3ZWV0XShodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZy93ZWIvcGFja2FnZXMvcnR3ZWV0KS4gT2J0ZW5pZG9zIGxvcyBkYXRvcyBzZSByZWFsaXrDsyB1bmEgc2VsZWNjacOzbiBkZSBjYW1wb3MgY29uIGVsIGZpbiBkZSByZWFsaXphciB1biBhbsOhbGlzaXMgZGUgdHdlZXRzIHByb3Bpb3MgeSByZXR3ZWV0cy4NCg0KTG9zIGzDrWRlcmVzIHNlbGVjY2lvbmFkb3Mgc29uOg0KDQp8ICoqTm8uKiogfCAqKk5vbWJyZXMqKiB8ICoqVXN1YXJpbyoqIHwgKipQYcOtcyoqfA0KfC0tLS0tLS18LS0tLS0tLXwtLS0tLS0tfC0tLS0tLS18DQp8IDEgfCBBbmRyw6lzIEFyYXV6IHwgZWN1YXJhdXogfCBFY3VhZG9yfA0KfCAyIHwgQWxiZXJ0byBGZXJuw6FuZGV6IHwgYWxmZXJkZXogfCBBcmdlbnRpbmF8DQp8IDMgfCBBbmRyw6lzIE1hbnVlbCB8IGxvcGV6b2JyYWRvcl8gfCBNw6l4aWNvfA0KfCA0IHwgQ3Jpc3RpbmEgS2lyY2huZXIgfCBDRktBcmdlbnRpbmEgfCBBcmdlbnRpbmF8DQp8IDUgfCBNaWd1ZWwgRMOtYXotQ2FuZWwgQmVybcO6ZGV6IHwgRGlhekNhbmVsQiB8IEN1YmF8DQp8IDYgfCBEaWxtYSBSb3Vzc2VmZiB8IGRpbG1hYnIgfCBCcmFzaWx8DQp8IDcgfCBFcm5lc3RvIFNhbXBlciB8IGVybmVzdG9zYW1wZXJwIHwgQ29sb21iaWF8DQp8IDggfCBFdm8gTW9yYWxlcyB8IGV2b2VzcHVlYmxvIHwgQm9saXZpYXwNCnwgOSB8IEZlcm5hbmRvIEx1Z28gfCBsdWdvX3B5IHwgUGFyYWd1YXl8DQp8IDEwIHwgR2FicmllbCBCb3JpYyB8IGdhYnJpZWxib3JpYyB8IENoaWxlfA0KfCAxMSB8IEd1c3Rhdm8gUGV0cm8gfCBwZXRyb2d1c3Rhdm8gfCBDb2xvbWJpYXwNCnwgMTIgfCBIdWdvIENow6F2ZXogfCBjaGF2ZXpjYW5kYW5nYSB8IFZlbmV6dWVsYXwNCnwgMTMgfCBMdWlzIEFyY2UgfCBMdWNob1hCb2xpdmlhIHwgQm9saXZpYXwNCnwgMTQgfCBMdWxhIHwgTHVsYU9maWNpYWwgfCBCcmFzaWx8DQp8IDE1IHwgTWFudWVsIFplbGF5YSB8IG1hbnVlbHpyIHwgSG9uZHVyYXN8DQp8IDE2IHwgTmljb2zDoXMgTWFkdXJvIHwgTmljb2xhc01hZHVybyB8IFZlbmV6dWVsYXwNCnwgMTcgfCBPbGxhbnRhIEh1bWFsYSB8IE9sbGFudGFfSHVtYWxhVCB8IFBlcsO6fA0KfCAxOCB8IFBlZHJvIENhc3RpbGxvIHwgUGVkcm9DYXN0aWxsb1RlIHwgUGVyw7p8DQp8IDE5IHwgUmFmYWVsIENvcnJlYSB8IE1hc2hpUmFmYWVsIHwgRWN1YWRvcnwNCnwgMjAgfCBYaW9tYXJhIENhc3RybyBkZSBaZWxheWEgfCBYaW9tYXJhQ2FzdHJvWiB8IEhvbmR1cmFzfA0KDQpEZXBlbmRpZW5kbyBkZWwgYW7DoWxpc2lzIHNlIGNvbnNpZGVyw7MgYSB0b2RvcyBsb3MgdXN1YXJpb3MgY29tbyB1bm8gc29sbywgc2luIGVtYmFyZ28sIGxvIGlkZWFsIGVzIHRyYWJhamFyIHBvciBzZXBhcmFkbyBwYXJhIGNhZGEgdXN1YXJpbyBwYXJhIGRldGVybWluYXIgbGFzIGRpZmVyZW5jaWFzIHkgc2ltaWxpdHVkZXMgcXVlIHB1ZWRhbiBleGlzdGlyLiBMb3MgZGF0b3MgcmVjb3BpbGFkb3MgY3VlbnRhbiBjb24gNTg1MTIgcmVnaXN0cm9zIHF1ZSBlbnRyZSBzdXMgcHJpbmNpcGFsZXMgY2FyYWN0ZXLDrXN0aWNhcyBwZXJtaXRlbiBkZXRlcm1pbmFyIHNpIHVuIHR3ZWV0IGVzIHByb3BpbyBvIHJldHdlZXQuDQoNCkEgY29udGludWFjacOzbiwgbGEgY2FyZ2EgZGUgbG9zIGRhdG9zIHkgbGEgdmlzdWFsaXphY2nDs24gZGUgbG9zIHByaW1lcm9zIHJlZ2lzdHJvczoNCg0KYGBge3IsIGVjaG89RkFMU0V9DQp0d2VldHMgPC0gZGF0b3MNCmhlYWQodHdlZXRzKQ0KYGBgDQoNCg0KIyBEZXNjcmlwY2nDs24gIGRlbCB0aXBvIGRlIHR3ZWV0Lg0KDQpFbCBjYW1wbyB0ZXh0byBhbG1hY2VuYSBhIGNhZGEgdHdlZXQgcmVjb3BpbGFkbywgZXMgaW1wb3J0YW50ZSBub3RhciBxdWUgdW4gdHdlZXQgcHVlZGUgc2VyIGRlIGF1dG9yw61hIHByb3BpYSBvIG5vLCBlbiBlc3RlIGNhc28gaGFibGFtb3MgZGUgdW4gcmV0d2VldC4gUGFyYSBlc3RvIHNlIHV0aWxpesOzIGFsIHBhcXVldGUgW3RpZHl2ZXJzZV0oaHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvd2ViL3BhY2thZ2VzL3RpZHl2ZXJzZS8pIHF1ZSBkZWJlcyBhcHJlbmRlciBzaSB0ZSBpbnRlcmVzYSBtYW5pcHVsYXIgZGF0b3MgY29uIFtSXShodHRwczovL3d3dy5yLXByb2plY3Qub3JnLykuIEEgY29udGludWFjacOzbiwgbGEgY2FudGlkYWQgZGUgdHdlZXRzIHBvciB0aXBvIHkgcG9yIHVzdWFyaW86DQoNCmBgYHtyLCB3YXJuaW5nPUZBTFNFfQ0KbGlicmFyeSh0aWR5dmVyc2UpDQoNCm1lcmdlKHggPSB0d2VldHMgJT4lICN0d2VldHMgcHJvcGlvcw0KICAgICAgICBzZWxlY3Qoc2NyZWVuX25hbWUsIGlzX3JldHdlZXQpICU+JSAjc2VsZWNjacOzbiBkZSBjYW1wb3MNCiAgICAgICAgZmlsdGVyKGlzX3JldHdlZXQgPT0gRkFMU0UpICU+JSAjZmlsdHJvIHBhcmEgdHdlZXQgcHJvcGlvDQogICAgICAgIGNvdW50KHNjcmVlbl9uYW1lLCBuYW1lID0gIm5fcHJvcGlvcyIpLCAjY2FudGlkYWQgZGUgdHdlZXRzIHByb3Bpb3MNCiAgICAgIHkgPSB0d2VldHMgJT4lICAjcmV0d2VldHMNCiAgICAgICAgc2VsZWN0KHNjcmVlbl9uYW1lLCBpc19yZXR3ZWV0KSAlPiUgI3NlbGVjY2nDs24gZGUgY2FtcG9zDQogICAgICAgIGZpbHRlcihpc19yZXR3ZWV0ID09IFRSVUUpICU+JSAjZmlsdHJvIHBhcmEgcmV0d2VldA0KICAgICAgICBjb3VudChzY3JlZW5fbmFtZSwgbmFtZSA9ICJuX3JldHdlZXRzIiksICNjYW50aWRhZCBkZSByZXR3ZWV0cw0KICAgICAgYWxsID0gVFJVRSkgJT4lICN1bmnDs24gZGUgZGF0YWZyYW1lcw0KICBhcnJhbmdlKHNjcmVlbl9uYW1lKSAjb3JkZW4gcG9yIHNjcmVlbl9uYW1lDQpgYGANCkJhc2FkbyBlbiBlbCByZXN1bHRhZG8gYW50ZXJpb3IsIGV4aXN0ZW4gdXN1YXJpb3MgcXVlIG5vIHJlYWxpemFuIHJldHdlZXRzLCBlbnRvbmNlcyBzZSBhbmFsaXphIG51ZXZhbWVudGUgYSB0b2RvcyBsb3MgdXN1YXJpb3MgY29tbyB1bm8gc29sby4gQSBjb250aW51YWNpw7NuLCBsYSBjYW50aWRhZCBkZSB0d2VldHMgcHJvcGlvcyB5IHJldHdlZXRzOg0KDQpgYGB7cn0NCiMgUHJvcGlvcw0KdHdlZXRzX3Byb3BpbyA9IHR3ZWV0cyAlPiUgZmlsdGVyKGlzX3JldHdlZXQ9PUZBTFNFKQ0KIyBSZXR3ZWV0cw0KdHdlZXRzX3JldHdlZXRzID0gdHdlZXRzICU+JSBmaWx0ZXIoaXNfcmV0d2VldD09VFJVRSkNCiMgQ2FudGlkYWQgZGUgdHdlZXRzIHByb3Bpb3MgeSBkZSByZXR3ZWV0cw0KQ2FudGlkYWRUaXBvID0gZGF0YS5mcmFtZSh0aXBvPWMoIlByb3BpbyIsIlJldHdlZXRzIiksICNjYW1wbyB0aXBvIGRlIHR3ZWV0DQogICAgICAgICAgICAgICAgICAgICAgICAgY2FudGlkYWQ9Yyhucm93KHR3ZWV0c19wcm9waW8pLCBucm93KHR3ZWV0c19yZXR3ZWV0cykpICNjYW1wbyBjYW50aWRhZCBkZSBjYWRhIHRpcG8NCiAgICAgICAgICAgICAgICAgICAgICAgICApDQpDYW50aWRhZFRpcG8NCmBgYA0KDQpQYXJhIHZpc3VhbGl6YXIgZXN0w6EgcmVsYWNpw7NuIGludHJvZHVjaW1vcyB1biBwZXNvIGEgY2FkYSB0aXBvLCBlbiBlc3RlIGNhc28gc3UgcG9yY2VudGFqZSwgeSBjcmVhbW9zIHVuIGdyw6FmaWNvIHBvciBibG9xdWVzLiBQYXJhIGVzdG8gc2UgdXRpbGl6YSBhbCBwYXF1ZXRlIFt3YWZmbGVdKGh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnL3dlYi9wYWNrYWdlcy93YWZmbGUvKSBxdWUgZW50cmUgc3VzIGZ1bmNpb25lcyBwZXJtaXRlIHJlYWxpemFyIHVuIGdyw6FmaWNvIHBvciBibG9xdWVzLg0KDQpgYGB7ciAsIHdhcm5pbmc9RkFMU0UsIGVjaG89VFJVRX0NCiMgQXVtZW50YW5kbyBwZXNvIGEgY2FkYSB0aXBvDQpDYW50aWRhZFRpcG8gPSBDYW50aWRhZFRpcG8gJT4lIA0KICBtdXRhdGUocGVzbz1yb3VuZChjYW50aWRhZC9zdW0oY2FudGlkYWQpKjEwMCwwMCkpICU+JSAjQ3JlYWNpb24gZGUgY2FtcG8gcGVzbw0KICBhcnJhbmdlKGRlc2MoY2FudGlkYWQpKSAjb3JkZW5hbmRvIHBvciBjYW50aWRhZA0KDQojIEV4dHJhY2Npb24gZGVsIHBlc28NCnBhcnRlID0gQ2FudGlkYWRUaXBvJHBlc28NCiMgRXRpcXVldGFuZG8gcG9yIGNsYXNlDQpuYW1lcyhwYXJ0ZSkgPSBDYW50aWRhZFRpcG8kdGlwbw0KIyBHcmFmaWNhIGRlIHRpcG8gZGUgdHdlZXQNCmxpYnJhcnkod2FmZmxlKQ0Kd2FmZmxlKHBhcnRlLCANCiAgICAgICByb3dzID0gNSwgI27Dum1lcm8gZGUgZmlsYXMgDQogICAgICAgdGl0bGUgPSAnVGlwbyBkZSB0d2VldHMnLCANCiAgICAgICBjb2xvcnMgPSBjb2xvclJhbXBQYWxldHRlKGMoJ2JsdWUnLCAncmVkJykpKDIpLCAjcGFsZXRhIGRlIGNvbG9yZXMNCiAgICAgICAjICgyKSBpbmRpY2EgZWwgbsO6bWVybyBkZSBjbGFzZXMgcXVlIHZhIGEgZ3JhZmljYXINCiAgICAgICBwYWQgPSAxLCAjZGVuc2lkYWQNCiAgICAgICBzaXplID0gMC41LCAjc2VwYXJhY2lvbiBlbnRyZSBibG9xdWVzICANCiAgICAgICBmbGlwID0gRkFMU0UsI29yaWVudGFjaW9uIHZlcnRpY2FsIGNvbiBUUlVFDQogICAgICAgeGxhYiA9ICAiRnVlbnRlOiBUd2l0dGVyIg0KICAgICAgICkNCmBgYA0KDQpPYnNlcnZhbW9zIHF1ZSBlcyBzdSBtYXlvcsOtYSBzZSBjcmVhbiB0d2VldHMgcHJvcGlvcy4gDQoNCiMgVHdlZXRzIHBvcHVsYXJlcw0KDQpTZSBkZXRlcm1pbmEgZWwgdHdlZXQgcHJvcGlvIG3DoXMgcG9wdWxhciBwb3IgdXN1YXJpbywgaWRlbnRpZmljYW5kbyBhIGxhIGNhbnRpZGFkIGRlIG1lIGd1c3RhIHJlY2liaWRvcy4NCg0KYGBge3J9DQp0d2VldHMgJT4lIA0KICBmaWx0ZXIoaXNfcmV0d2VldCA9PSBGQUxTRSkgJT4lICNmaWx0cm8gcGFyYSB0d2VldCBwcm9waW8NCiAgZ3JvdXBfYnkoc2NyZWVuX25hbWUpICU+JSAjYWdydXBhY2lvbiBwb3Igc2NyZWVuX25hbWUNCiAgbXV0YXRlKHBvcHVsYXIgPSBtYXgoZmF2b3JpdGVfY291bnQpKSAlPiUgI2NyZWFjacOzbiBkZSBjYW1wbw0KICBmaWx0ZXIoZmF2b3JpdGVfY291bnQgPT0gcG9wdWxhcikgJT4lICNmaWx0cm8gcGFyYSBzZWxlY2Npb25hciBhbCB0d2VldCBtw6FzIHBvcHVsYXINCiAgc2VsZWN0KHNjcmVlbl9uYW1lLCBwb3B1bGFyLCB0ZXh0KSAlPiUgIyBzZWxlY2Npw7NuIGRlIGNhbXBvcw0KICBhcnJhbmdlKGRlc2MocG9wdWxhcikpICNvcmRlbiBwb3IgcG9wdWxhcg0KYGBgDQoNCkVsIHJldHdlZXQgIG3DoXMgcG9wdWxhciBwb3IgdXN1YXJpbyB0YW1iacOpbiBlcyBpZGVudGlmaWNhZG8uDQoNCmBgYHtyfQ0KdHdlZXRzICU+JSANCiAgZmlsdGVyKGlzX3JldHdlZXQgPT0gVFJVRSkgJT4lICNmaWx0cm8gcGFyYSByZXR3ZWV0DQogIGdyb3VwX2J5KHNjcmVlbl9uYW1lKSAlPiUgI2FncnVwYWNpb24gcG9yIHNjcmVlbl9uYW1lDQogIG11dGF0ZShwb3B1bGFyID0gbWF4KHJldHdlZXRfZmF2b3JpdGVfY291bnQpKSAlPiUgI2NyZWFjacOzbiBkZSBjYW1wbw0KICBmaWx0ZXIocmV0d2VldF9mYXZvcml0ZV9jb3VudCA9PSBwb3B1bGFyKSAlPiUgI2ZpbHRybyBwYXJhIHNlbGVjY2lvbmFyIGFsIHR3ZWV0IG3DoXMgcG9wdWxhcg0KICBzZWxlY3Qoc2NyZWVuX25hbWUsIHBvcHVsYXIsIHRleHQpICU+JSAjIHNlbGVjY2nDs24gZGUgY2FtcG9zDQogIGFycmFuZ2UoZGVzYyhwb3B1bGFyKSkgI29yZGVuIHBvciBwb3B1bGFyDQpgYGANCg0KIyBBbsOhbGlzaXMgZGUgaGFzaHRhZ3MNCg0KRGVudHJvIGRlIGNhZGEgcHVibGljYWNpw7NuIGVzIGNvbcO6biBhw7FhZGlyIGFsZ8O6biBoYXNodGFnLiBTZSBkZXRlcm1pbsOzIGxvcyAyMCBoYXNodGFncyBtw6FzIHBvcHVsYXJlcyBwb3IgdGlwbyBkZSB0d2VldCBjb25zaWRlcmFuZG8gYSB0b2RvcyBjb21vIHVuby4gUHJpbWVybyBsb3MgaGFzaHRhZ3MgcmVsYWNpb25hZG9zIGNvbiB0d2VldHMgcHJvcGlvcy4NCg0KDQpgYGB7ciwgd2FybmluZz1GQUxTRX0NCnR3ZWV0c19oYXNodGFnIDwtIHR3ZWV0c19wcm9waW8gJT4lIA0KICBmaWx0ZXIoIWlzLm5hKGhhc2h0YWdzKSkgJT4lIA0KICBzZWxlY3QoaGFzaHRhZ3MpDQoNCnRpYmJsZSh3b3JkID0gdW5saXN0KHR3ZWV0c19oYXNodGFnJGhhc2h0YWdzKSkgJT4lICNleHRyYWVyIGhhc2h0YWcNCiAgY291bnQod29yZCwgbmFtZSA9ICJjYW50aWRhZCIsIHNvcnQgPSBUUlVFKSAlPiUgI2NvbnRhciBoYXNodGFncw0KICBoZWFkKDIwKSAlPiUgI3NlbGVjY2lvbmFyIGxvcyAyMCBwcmltZXJvcw0KICBtdXRhdGUod29yZDEgPSByZW9yZGVyKHdvcmQsIGNhbnRpZGFkKSkgJT4lICNvcmRlbmFyDQogIGdncGxvdChhZXMoeCA9IHdvcmQxLCB5ID0gY2FudGlkYWQpKSArICNjcmVhciBsYSBncmFmaWNhDQogIGdlb21fY29sKGNvbG91ciA9ICJ3aGl0ZSIsIGZpbGwgPSAiYmx1ZSIpICsgI2FuYWRlIGxhIGdyw6FmaWNhIGRlIGNhbnRpZGFkDQogIHhsYWIoTlVMTCkgKyAjcXVpdGEgZWwgbm9tYnJlIGRlbCBlamUgaG9yaXpvbnRhbA0KICBjb29yZF9mbGlwKCkgKyAjZ3JhZmljYSBlbiB2ZXJ0aWNhbA0KICB0aGVtZV9saWdodCgpICArICN0ZW1hIHNlbGVjY2lvbmFkbw0KICBsYWJzKHkgPSAiRnJlY3VlbmNpYSIsDQogICAgICAgeCA9ICJIYXNodGFncyIsDQogICAgICAgdGl0bGUgPSBwYXN0ZSgiSGFzaHRhZ3MgbcOhcyBmcmVjdWVudGVzIGVuIHR3ZWV0cyBwcm9waW9zIiksDQogICAgICAgc3VidGl0bGUgPSAiQ2FudGlkYWQgZGUgaGFzaHRhZ3MiLCANCiAgICAgICBjYXB0aW9uID0gIkZ1ZW50ZTogVHdpdHRlciIpDQpgYGANCg0KQSBjb250aW51YWNpw7NuLCBsb3MgaGFzaHRhZ3MgcmVsYWNpb25hZG9zIGNvbiBsb3MgcmV0d2VldHMuDQoNCmBgYHtyfQ0KdHdlZXRzX2hhc2h0YWdfciA8LSB0d2VldHNfcmV0d2VldHMgJT4lIA0KICBmaWx0ZXIoIWlzLm5hKGhhc2h0YWdzKSkgJT4lIA0KICBzZWxlY3QoaGFzaHRhZ3MpDQoNCnRpYmJsZSh3b3JkID0gdW5saXN0KHR3ZWV0c19oYXNodGFnX3IkaGFzaHRhZ3MpKSAlPiUgI2V4dHJhZXIgaGFzaHRhZw0KICBjb3VudCh3b3JkLCBuYW1lID0gImNhbnRpZGFkIiwgc29ydCA9IFRSVUUpICU+JSAjY29udGFyIGhhc2h0YWdzDQogIGhlYWQoMjApICU+JSAjc2VsZWNjaW9uYXIgbG9zIDIwIHByaW1lcm9zDQogIG11dGF0ZSh3b3JkMSA9IHJlb3JkZXIod29yZCwgY2FudGlkYWQpKSAlPiUgI29yZGVuYXINCiAgZ2dwbG90KGFlcyh4ID0gd29yZDEsIHkgPSBjYW50aWRhZCkpICsgI2NyZWFyIGxhIGdyYWZpY2ENCiAgZ2VvbV9jb2woY29sb3VyID0gIndoaXRlIiwgZmlsbCA9ICJibHVlIikgKyAjYW5hZGUgbGEgZ3LDoWZpY2EgZGUgY2FudGlkYWQNCiAgeGxhYihOVUxMKSArICNxdWl0YSBlbCBub21icmUgZGVsIGVqZSBob3Jpem9udGFsDQogIGNvb3JkX2ZsaXAoKSArICNncmFmaWNhIGVuIHZlcnRpY2FsDQogIHRoZW1lX2xpZ2h0KCkgICsgI3RlbWEgc2VsZWNjaW9uYWRvDQogIGxhYnMoeSA9ICJGcmVjdWVuY2lhIiwNCiAgICAgICB4ID0gIkhhc2h0YWdzIiwNCiAgICAgICB0aXRsZSA9IHBhc3RlKCJIYXNodGFncyBtw6FzIGZyZWN1ZW50ZXMgZW4gcmV0d2VldHMiKSwNCiAgICAgICBzdWJ0aXRsZSA9ICJDYW50aWRhZCBkZSBoYXNodGFncyIsIA0KICAgICAgIGNhcHRpb24gPSAiRnVlbnRlOiBUd2l0dGVyIikNCmBgYA0KDQpDb21vIHNlIG9ic2VydmEsIGxvcyBoYXNodGFncyBlbiBzdSBtYXlvcsOtYSBzZSBkaWZlcmVuY2lhbiBkZSB0d2VldHMgcHJvcGlvcyBjb24gcmV0d2VldHMuDQoNCiMgRnJlY3VlbmNpYSBkZSBsb3MgdHdlZXRzDQoNCk90cmEgZGVzY3JpcGNpw7NuIGludGVyZXNhbnRlIGVzIGRldGVybWluYXIgbGEgZnJlY3VlbmNpYSBlbiBxdWUgc2UgcHVibGljYS4gQSBjb250aW51YWNpw7NuLCBsYSByZXByZXNlbnRhY2nDs24gZ3LDoWZpY2EgZGUgbGEgZnJlY3VlbmNpYSBkZSB0d2VldHMgcHJvcGlvcyBwb3IgZMOtYSwgcGFyYSBlc3RvIHNlIHV0aWxpesOzIGEgKnRzX3Bsb3QqIGRlIFtydHdlZXRdKGh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnL3dlYi9wYWNrYWdlcy9ydHdlZXQpLCBlcyBpbXBvcnRhbnRlIG5vdGFyIHF1ZSB0YW1iacOpbiBzZSBwdWVkZSByZWFsaXphciBlc3RlIGFuw6FsaXNpcyBwb3Igc2VtYW5hLCBtZXMsIGHDsW8gc2ltcGxlbWVudGUgY2FtYmlhbmRvIGxhIHNlbGVjY2nDs24gZW4gZWwgYXJndW1lbnRvICpieSogZGUgKnRzX3Bsb3QqLg0KDQoNCmBgYHtyfQ0KbGlicmFyeShydHdlZXQpDQoNCnR3ZWV0c19wcm9waW8gJT4lIA0KICB0c19wbG90KGJ5ID0gImRheXMiLCBjb2xvciA9ICJibHVlIikgKw0KICB0aGVtZV9saWdodCgpKw0KICBsYWJzKHRpdGxlID0gIkZyZWN1ZW5jaWEgZGUgdHdlZXRzIHByb3Bpb3MiLA0KICAgICAgIHN1YnRpdGxlID0gIkNhbnRpZGFkIGRlIHR3ZWV0cyBwb3IgZMOtYSIsDQogICAgICAgeCA9ICJGZWNoYSIsDQogICAgICAgeSA9ICJDYW50aWRhZCIsDQogICAgICAgY2FwdGlvbiA9ICJGdWVudGU6IFR3aXR0ZXIiKQ0KYGBgDQoNCkRlIGZvcm1hIHNpbWlsYXIsIGxhIHJlcHJlc2VudGFjacOzbiBncsOhZmljYSBkZSBsYSBmcmVjdWVuY2lhIGRlIHJldHdlZXRzLg0KDQpgYGB7ciwgd2FybmluZz1GQUxTRX0NCnR3ZWV0c19yZXR3ZWV0cyAlPiUgDQogIHRzX3Bsb3QoYnkgPSAiZGF5cyIsIGNvbG9yID0gImJsdWUiKSArDQogIHRoZW1lX2xpZ2h0KCkrDQogIGxhYnModGl0bGUgPSAiRnJlY3VlbmNpYSBkZSByZXR3ZWV0cyIsDQogICAgICAgc3VidGl0bGUgPSAiQ2FudGlkYWQgZGUgcmV0d2VldHMgcG9yIGTDrWEiLA0KICAgICAgIHggPSAiRmVjaGEiLA0KICAgICAgIHkgPSAiQ2FudGlkYWQiLA0KICAgICAgIGNhcHRpb24gPSAiRnVlbnRlOiBUd2l0dGVyIikNCmBgYA0KDQoNCiMgUGFsYWJyYXMgbcOhcyB1c2FkYXMNCg0KT3RyYXMgZGVzY3JpcGNpb25lcyB1c3VhbGVzIHNvbiBkZXRlcm1pbmFyIGxhIGNhbnRpZGFkIGRlIHBhbGFicmFzIHV0aWxpemFkYXMsIGxhcyBwYWxhYnJhcyBtw6FzIHBvcHVsYXJlcyB5IHN1IHJlcHJlc2VudGFjacOzbi4gUGFyYSByZWFsaXphcmxhcywgZXMgaW1wb3J0YW50ZSBsaW1waWFyIGEgbG9zIGRhdG9zLCBwYXJhIGVzdG8gc2Ugc2VsZWNjaW9uw7MgYSBsb3MgcGFxdWV0ZXMgW3RpZHl0ZXh0XShodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZy93ZWIvcGFja2FnZXMvdGlkeXRleHQvaW5kZXguaHRtbCkgeSBbdG1dKGh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnL3dlYi9wYWNrYWdlcy90bS8pLiBFbCBwcm9jZWRpbWllbnRvIGVzIGluaWNpYXIgcmVtb3ZpZW5kbyBjYXJhY3RlcmVzIHF1ZSBubyB0ZW5nYW4gdW4gc2lnbmlmaWNhZG8gY29uY3JldG8uIFNlIGhhIHNlZ3VpZG8gZWwgc2lndWllbnRlIG9yZGVuOiByZW1vdmVyIGhpcGVydsOtbmN1bG9zLCByZW1vdmVyIG1lbmNpb25lcywgcmVtb3ZlciBzZXBhcmFkb3JlcywgcmVtb3ZlciBuw7ptZXJvcyB5IHJlbW92ZXIgZXNwYWNpb3MgZW4gYmxhbmNvLiBMdWVnbywgZXMgaW1wb3J0YW50ZSByZW1vdmVyIGFjZW50b3MgeSBjYW1iaWFyIHRvZGFzIGxhcyBsZXRyYXMgYSBtaW7DunNjdWxhcyBvIG1hecO6c2N1bGFzLg0KDQpgYGB7ciwgd2FybmluZz1GQUxTRX0NCmxpYnJhcnkodGlkeXRleHQpDQpsaWJyYXJ5KHRtKQ0KDQpwYWxhYnJhcyA8LSB0d2VldHNfcHJvcGlvICU+JQ0KICBmaWx0ZXIoaXNfcmV0d2VldCA9PSBGQUxTRSkgJT4lDQogIHNlbGVjdCh0ZXh0KSAlPiUNCiAgbXV0YXRlKHRleHQ9c3RyX3JlcGxhY2VfYWxsKHRleHQsICJodHRwc1xcUyoiLCAiIikpICU+JSAjcmVtb3ZlciBoaXBlcnbDrW5jdWxvcw0KICBtdXRhdGUodGV4dD1zdHJfcmVwbGFjZV9hbGwodGV4dCwgIkBcXFMqIiwgIiIpKSAlPiUgI3JlbW92ZXIgbWVuY2lvbmVzDQogIG11dGF0ZSh0ZXh0PXN0cl9yZXBsYWNlX2FsbCh0ZXh0LCAiW1xyXG5cdF0iLCAiIikpICU+JSAjcmVtb3ZlciBzZXBhcmFkb3Jlcw0KICBtdXRhdGUodGV4dD1yZW1vdmVOdW1iZXJzKHRleHQpKSAlPiUgI3JlbW92ZXIgbsO6bWVyb3MNCiAgbXV0YXRlKHRleHQ9cmVtb3ZlUHVuY3R1YXRpb24odGV4dCkpICU+JSAjcmVtb3ZlciBwdW50dWFjaW9uDQogIG11dGF0ZSh0ZXh0PXN0cl9zcXVpc2godGV4dCkpICU+JSAjcmVtb3ZlciBlc3BhY2lvcyBlbiBibGFuY28NCiAgbXV0YXRlKHRleHQ9dG9sb3dlcih0ZXh0KSkgI2EgbWluw7pzY3VsYXMNCmBgYA0KDQpDb24gZWwgdGV4dG8gbGltcGlvLCBzZSBwcm9jZWRlIGEgcmVhbGl6YXIgbGEgdG9rZW5pemFjacOzbi4gRXN0byBzaWduaWZpY2EgcXVlIHZhbW9zIGEgZGl2aWRpciBhIGNhZGEgdHdlZXQgZW4gdW5pZGFkZXMgZGUgdGV4dG8sIGVuIGVzdGUgY2FzbyBlbiBwYWxhYnJhcy4gRXhpc3RlIHVuYSBpbnRlcmVzYW50ZSB0ZW9yw61hIGFsIHJlc3BlY3RvLCB5YSBxdWUgc2kgc2UgZGVzZWEgbGFzIHVuaWRhZGVzIHB1ZWRlbiBzZXIgZG9zIHBhbGFicmFzIHNlZ3VpZGFzIHUgb3JhY2lvbmVzLCBwb3IgZWplbXBsbywgc2kgYWxndWllbiBzZSBpbnRlcmVzYSBwdWVkZSBsZWVyIGFsIHJlc3BlY3RvIGVuIGVsIGNhcMOtdHVsbyAxIGRlIFtUZXh0IE1pbmluZyB3aXRoIFI6IEEgVGlkeSBBcHByb2FjaF0oaHR0cHM6Ly93d3cudGlkeXRleHRtaW5pbmcuY29tL3RpZHl0ZXh0Lmh0bWwpLg0KDQpgYGB7ciwgd2FybmluZz1GQUxTRX0NCnBhbGFicmFzIDwtIHBhbGFicmFzICU+JSANCiAgdW5uZXN0X3Rva2Vucyh3b3JkLCB0ZXh0LCB0b19sb3dlciA9IEYpICN0b2tlbml6YWNpb24NCg0KcGFsYWJyYXMNCmBgYA0KDQpBbCB2aXN1YWxpemFyIGxhcyBwcmltZXJhcyBwYWxhYnJhcyBvYnRlbmlkYXMgZW4gZWwgZGF0YWZyYW1lICpwYWxhYnJhcyogc2Ugb2JzZXJ2YW4gcGFsYWJyYXMgY29tbyAibGEiLCAiZGUiLCAiZXMiLCBlbnRyZSBvdHJhcywgcXVlIGxsYW1hbiBsYSBhdGVuY2nDs24geWEgcXVlIHVubyBlc3BlcmFyw61hIHF1ZSBhcGFyZXpjYW4gbXVjaGFzIHZlY2VzIGVuIHVuIGRldGVybWluYWRvIHRleHRvLiBVbmEgcHLDoWN0aWNhIGNvbcO6biBlcyByZW1vdmVyIGEgZXN0ZSB0aXBvIGRlIHBhbGFicmFzLCBkZW5vbWluYWRhcyBzdG9wd29yZHMuIEVuIGxhIGRvY3VtZW50YWNpw7NuIGRlIGxvcyBwYXF1ZXRlcyB1dGlsaXphZG9zIHNlIHB1ZWRlIGFwcmVuZGVyIHVuIHBvY28gbcOhcyBhbCByZXNwZWN0by4gRW4gZXN0ZSBjYXNvLCBhbCBjb250YXIgY29uIHVzdWFyaW9zIHF1ZSBoYWJsYW4gZXNwYcOxb2wsIGluZ2zDqXMgeSBwb3J0dWd1w6lzIHNlIHJlbW92aWVyb24gc3RvcHdvcmRzIHBhcmEgZXN0b3MgdHJlcyBpZGlvbWFzLiBBZGVtw6FzLCBzZSBjb250YWJpbGl6w7MgYSBjYWRhIHBhbGFicmEgeSBvcmRlbsOzIGVuIGZvcm1hIGRlc2NlbmRlbnRlIGNvbnNpZGVyYW5kbyBlbCBuw7ptZXJvIGRlIGFwYXJpY2lvbmVzLg0KDQpgYGB7ciwgd2FybmluZz1GQUxTRX0NCnBhbGFicmFzIDwtIHBhbGFicmFzICU+JSANCiAgZmlsdGVyKCF3b3JkICVpbiUgc3RvcHdvcmRzKCJzcGFuaXNoIikpICU+JQ0KICBmaWx0ZXIoIXdvcmQgJWluJSBzdG9wd29yZHMoInBvcnR1Z3Vlc2UiKSkgJT4lDQogIGZpbHRlcighd29yZCAlaW4lIHN0b3B3b3JkcygiZW5nbGlzaCIpKSAlPiUNCiAgY291bnQod29yZCwgc29ydCA9IFRSVUUpICU+JQ0KICBtdXRhdGUod29yZCA9IHJlb3JkZXIod29yZCwgbikpICU+JQ0KICBhcnJhbmdlKGRlc2MobikpDQoNCnBhbGFicmFzDQpgYGANCg0KT2JzZXJ2ZW1vcyBxdWUgbGFzIHBhbGFicmFzICLDqSIgeSAic2VyIiBhcGFyZWNlbiwgZXN0byBlcyBhbGdvIHF1ZSBlbiBwcmluY2lwaW8gbm8gZXNwZXJhbW9zIHN1Y2VkYSwgc2luIGVtYmFyZ28sIGFwcm92ZWNoYW1vcyBzdSBhcGFyaWNpw7NuIHBhcmEgaW5kaWNhciBjb21vIHJlbW92ZXIgcGFsYWJyYXMgZXNwZWNpYWxlcy4gU2ltcGxlbWVudGUgc2UgZGV0ZXJtaW5hIHVuIG51ZXZvIGNvbmp1bnRvIGRlIHBhbGFicmFzIGEgc2VyIHJlbW92aWRhcywgZW4gZXN0ZSBjYXNvIHNvbGFtZW50ZSBzZSBjb25zaWRlcsOzIGEgbGFzIHBhbGFicmFzIG1lbmNpb25hZGFzIGFudGVzLCBwZXJvIHNpIHNlIHRyYWJhamEgY29uIGFsZ8O6biBvdHJvIHRleHRvIHF1ZSBpbmNsdXlhIG11Y2hvcyBzw61tYm9sb3MgbyBwYWxhYnJhcyBlc3BlY2lhbGVzIGNvbW8gdmFyaWFibGVzIGNvbiBzdWLDrW5kaWNlcywgZWwgY29uc2lkZXJhciBlc3TDoSBvcGNpw7NuIHB1ZWRlIHNlciBkZSBncmFuIHV0aWxpZGFkLg0KDQpgYGB7ciwgd2FybmluZz1GQUxTRX0NCm15X3N0b3B3b3JkIDwtIHRpYmJsZSh3b3JkID0gYygiw6kiLCAic2VyIikpDQoNCnBhbGFicmFzIDwtIHBhbGFicmFzICU+JSANCiAgYW50aV9qb2luKG15X3N0b3B3b3JkKQ0KDQpwYWxhYnJhcw0KYGBgDQoNClBhcmEgZmluYWxpemFyLCBzZSBjcmXDsyB1bmEgbnViZSBkZSBwYWxhYnJhcyBxdWUgZXMgdW5hIGhlcnJhbWllbnRhIHZpc3VhbCBtdXkgYW1pZ2FibGUgcXVlIHBlcm1pdGUgZGV0ZXJtaW5hciBwb3IgZWwgdGFtYcOxbyBkZSBsYSBwYWxhYnJhIHN1IGltcG9ydGFuY2lhIHlhIHF1ZSByZXByZXNlbnRhIGFzw60gc3UgZnJlY3VlbmNpYSBkZSBhcGFyaWNpw7NuLiBQYXJhIGVzdG8gc2UgdXRpbGl6w7MgYWwgcGFxdWV0ZSBbd29yZGNsb3VkMl0oaHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvd2ViL3BhY2thZ2VzL3dvcmRjbG91ZDIvKSBxdWUgY3VlbnRhIGNvbiBjYXJhY3RlcsOtc3RpY2FzIHF1ZSBwZXJtaXRlbiByZWFsaXphciBudWJlcyBtdXkgYW1pZ2FibGVzLiBQb3IgZWplbXBsbywgdXNhciB1biBmb25kbyBncmlzLCBjb2xvcmVzIHBhc3RlbCBwYXJhIGxhcyBwYWxhYnJhcyB5IGRhcmxlIHVuYSBmb3JtYSBkZSBjb3JhesOzbi4NCg0KYGBge3IsIHdhcm5pbmc9RkFMU0V9DQpsaWJyYXJ5KHdvcmRjbG91ZDIpDQpwYWxhYnJhcyAlPiUgDQogIHNlbGVjdCh3b3JkLCBmcmVxPW4pICU+JSANCiAgd29yZGNsb3VkMihzaGFwZSA9ICdjYXJkaW9pZCcsIHNpemUgPSAwLjI1LCBjb2xvciA9ICJyYW5kb20tbGlnaHQiLCBiYWNrZ3JvdW5kQ29sb3IgPSAiZ3JleSIpDQpgYGANCg0KRWwgZmluIGRlIGVzdGUgYXJjaGl2byB5IGxvcyBxdWUgdmVuZHLDoW4gZXMgY29tcGFydGlyIGxvIHF1ZSBoZSBhcHJlbmRpZG8uIEVzIHBvc2libGUgaGFjZXIgYW7DoWxpc2lzIG3DoXMgcHJvZnVuZG9zIHkgZGV0YWxsYWRvcyBxdWUgZXNwZXJvIHNlZ3VpciBjb21wYXJ0aWVuZG8sIHBvciBlamVtcGxvLCBlbCByZWFsaXphciB1biBhbsOhbGlzaXMgZGUgc2VudGltaWVudG9zIHkgdW4gY2xhc2lmaWNhZG9yIGJhc2FkbyBlbiB1biBhbsOhbGlzaXMgZGUgc2VudGltaWVudG9zLg0KDQpFbGFib3JhZG8gcG9yIEphaXJvIFJvamFzLg0K